<?php
namespace app\common\services;

use support\Redis;
use support\Log;

/**
 * 设备指纹服务
 * 生成和验证设备指纹，用于设备识别和归因
 */
class DeviceFingerprintService
{
    /**
     * 指纹版本
     */
    const FINGERPRINT_VERSION = '2.0';
    
    /**
     * 指纹缓存时间 (24小时)
     */
    const FINGERPRINT_CACHE_TTL = 86400;
    
    /**
     * 相似度阈值
     */
    const SIMILARITY_THRESHOLD = 0.85;

    /**
     * 生成增强设备指纹
     */
    public function generateFingerprint($deviceInfo, $existingFingerprint = '')
    {
        // 标准化设备信息
        $normalizedInfo = $this->normalizeDeviceInfo($deviceInfo);
        
        // 生成基础指纹
        $baseFingerprint = $this->generateBaseFingerprint($normalizedInfo);
        
        // 增强指纹生成
        $enhancedFingerprint = $this->enhanceFingerprint($baseFingerprint, $normalizedInfo);
        
        // 指纹冲突检测
        $finalFingerprint = $this->resolveFingerprintConflicts($enhancedFingerprint, $existingFingerprint);
        
        // 缓存指纹信息
        $this->cacheFingerprint($finalFingerprint, $normalizedInfo);
        
        return $finalFingerprint;
    }

    /**
     * 验证设备指纹
     */
    public function validateFingerprint($fingerprint, $deviceInfo)
    {
        // 获取缓存的指纹信息
        $cachedInfo = $this->getCachedFingerprint($fingerprint);
        
        if (!$cachedInfo) {
            return [
                'valid' => false,
                'reason' => 'Fingerprint not found in cache',
                'confidence' => 0
            ];
        }
        
        // 计算相似度
        $similarity = $this->calculateSimilarity($deviceInfo, $cachedInfo['device_info']);
        
        // 验证结果
        $isValid = $similarity >= self::SIMILARITY_THRESHOLD;
        
        return [
            'valid' => $isValid,
            'similarity' => $similarity,
            'confidence' => $isValid ? $similarity : 0,
            'reason' => $isValid ? 'Fingerprint validated successfully' : 'Low similarity score',
            'details' => $this->getValidationDetails($deviceInfo, $cachedInfo['device_info'])
        ];
    }

    /**
     * 标准化设备信息
     */
    private function normalizeDeviceInfo($deviceInfo)
    {
        $normalized = [
            'platform' => $this->normalizePlatform($deviceInfo['platform'] ?? ''),
            'os_version' => $this->normalizeVersion($deviceInfo['os_version'] ?? ''),
            'device_model' => $this->normalizeDeviceModel($deviceInfo['device_model'] ?? ''),
            'screen_resolution' => $this->normalizeResolution($deviceInfo['screen_resolution'] ?? ''),
            'user_agent' => $this->normalizeUserAgent($deviceInfo['user_agent'] ?? ''),
            'language' => $this->normalizeLanguage($deviceInfo['language'] ?? ''),
            'timezone' => $this->normalizeTimezone($deviceInfo['timezone'] ?? ''),
            'cpu_cores' => intval($deviceInfo['cpu_cores'] ?? 0),
            'memory' => intval($deviceInfo['memory'] ?? 0),
            'storage' => intval($deviceInfo['storage'] ?? 0),
            'battery_level' => $this->normalizeBatteryLevel($deviceInfo['battery_level'] ?? null),
            'network_type' => $this->normalizeNetworkType($deviceInfo['network_type'] ?? ''),
            'device_id' => $deviceInfo['device_id'] ?? '',
            'android_id' => $deviceInfo['android_id'] ?? '',
            'idfa' => $deviceInfo['idfa'] ?? '',
            'idfv' => $deviceInfo['idfv'] ?? '',
            'imei' => $deviceInfo['imei'] ?? '',
            'mac_address' => $this->normalizeMacAddress($deviceInfo['mac_address'] ?? ''),
            'bluetooth_mac' => $this->normalizeMacAddress($deviceInfo['bluetooth_mac'] ?? ''),
            'fonts' => $this->normalizeFonts($deviceInfo['fonts'] ?? []),
            'plugins' => $this->normalizePlugins($deviceInfo['plugins'] ?? []),
            'canvas_hash' => $deviceInfo['canvas_hash'] ?? '',
            'webgl_hash' => $deviceInfo['webgl_hash'] ?? '',
            'audio_hash' => $deviceInfo['audio_hash'] ?? '',
            'client_timestamp' => intval($deviceInfo['client_timestamp'] ?? time())
        ];
        
        // 移除空值和敏感信息
        $normalized = array_filter($normalized, function($value) {
            return $value !== '' && $value !== null && $value !== [];
        });
        
        return $normalized;
    }

    /**
     * 生成基础指纹
     */
    private function generateBaseFingerprint($normalizedInfo)
    {
        // 选择稳定的特征进行指纹生成
        $stableFeatures = [
            'platform',
            'device_model',
            'screen_resolution',
            'language',
            'timezone',
            'cpu_cores',
            'fonts',
            'plugins',
            'canvas_hash',
            'webgl_hash',
            'audio_hash'
        ];
        
        $fingerprintData = [];
        foreach ($stableFeatures as $feature) {
            if (isset($normalizedInfo[$feature])) {
                $fingerprintData[$feature] = $normalizedInfo[$feature];
            }
        }
        
        // 生成指纹哈希
        $fingerprintString = json_encode($fingerprintData, JSON_SORT_KEYS);
        $baseFingerprint = hash('sha256', $fingerprintString);
        
        return $baseFingerprint;
    }

    /**
     * 增强指纹生成
     */
    private function enhanceFingerprint($baseFingerprint, $normalizedInfo)
    {
        // 添加版本信息
        $enhancedData = [
            'version' => self::FINGERPRINT_VERSION,
            'base' => $baseFingerprint,
            'timestamp' => time(),
            'stability_score' => $this->calculateStabilityScore($normalizedInfo)
        ];
        
        // 添加设备行为特征
        $behavioralFeatures = $this->extractBehavioralFeatures($normalizedInfo);
        $enhancedData['behavioral'] = hash('sha256', json_encode($behavioralFeatures, JSON_SORT_KEYS));
        
        // 添加网络特征
        $networkFeatures = $this->extractNetworkFeatures($normalizedInfo);
        $enhancedData['network'] = hash('sha256', json_encode($networkFeatures, JSON_SORT_KEYS));
        
        // 生成最终增强指纹
        $enhancedString = json_encode($enhancedData, JSON_SORT_KEYS);
        $enhancedFingerprint = hash('sha256', $enhancedString);
        
        return $enhancedFingerprint;
    }

    /**
     * 解决指纹冲突
     */
    private function resolveFingerprintConflicts($newFingerprint, $existingFingerprint)
    {
        if (empty($existingFingerprint)) {
            return $newFingerprint;
        }
        
        // 检查现有指纹是否有效
        $existingInfo = $this->getCachedFingerprint($existingFingerprint);
        
        if ($existingInfo && $this->isFingerprintValid($existingInfo)) {
            // 如果现有指纹仍然有效，继续使用
            return $existingFingerprint;
        }
        
        // 检查是否存在相似指纹
        $similarFingerprint = $this->findSimilarFingerprint($newFingerprint);
        
        if ($similarFingerprint) {
            return $similarFingerprint;
        }
        
        return $newFingerprint;
    }

    /**
     * 计算稳定性分数
     */
    private function calculateStabilityScore($deviceInfo)
    {
        $score = 0;
        $totalWeight = 0;
        
        // 定义特征权重
        $weights = [
            'platform' => 10,
            'device_model' => 15,
            'screen_resolution' => 8,
            'language' => 5,
            'timezone' => 5,
            'cpu_cores' => 3,
            'fonts' => 12,
            'plugins' => 8,
            'canvas_hash' => 15,
            'webgl_hash' => 15,
            'audio_hash' => 10
        ];
        
        foreach ($weights as $feature => $weight) {
            if (isset($deviceInfo[$feature]) && !empty($deviceInfo[$feature])) {
                $score += $weight;
            }
            $totalWeight += $weight;
        }
        
        return $totalWeight > 0 ? round($score / $totalWeight, 2) : 0;
    }

    /**
     * 提取行为特征
     */
    private function extractBehavioralFeatures($deviceInfo)
    {
        $features = [];
        
        // 时间特征
        $hour = intval(date('H', $deviceInfo['client_timestamp'] ?? time()));
        $features['time_segment'] = $this->getTimeSegment($hour);
        
        // 设备使用模式
        if (isset($deviceInfo['battery_level'])) {
            $features['battery_segment'] = $this->getBatterySegment($deviceInfo['battery_level']);
        }
        
        // 网络使用模式
        if (isset($deviceInfo['network_type'])) {
            $features['network_preference'] = $deviceInfo['network_type'];
        }
        
        return $features;
    }

    /**
     * 提取网络特征
     */
    private function extractNetworkFeatures($deviceInfo)
    {
        $features = [];
        
        // IP地址特征 (不存储实际IP)
        $features['ip_class'] = $this->getIpClass();
        
        // 网络类型
        if (isset($deviceInfo['network_type'])) {
            $features['network_type'] = $deviceInfo['network_type'];
        }
        
        // User Agent特征
        if (isset($deviceInfo['user_agent'])) {
            $uaFeatures = $this->parseUserAgent($deviceInfo['user_agent']);
            $features = array_merge($features, $uaFeatures);
        }
        
        return $features;
    }

    /**
     * 查找相似指纹
     */
    private function findSimilarFingerprint($fingerprint)
    {
        // 获取所有缓存的指纹
        $pattern = 'device_fingerprint:*';
        $keys = Redis::keys($pattern);
        
        $bestMatch = null;
        $bestSimilarity = 0;
        
        foreach ($keys as $key) {
            $cachedData = Redis::get($key);
            if (!$cachedData) continue;
            
            $data = json_decode($cachedData, true);
            $similarity = $this->calculateFingerprintSimilarity($fingerprint, $data['fingerprint']);
            
            if ($similarity > $bestSimilarity && $similarity >= self::SIMILARITY_THRESHOLD) {
                $bestSimilarity = $similarity;
                $bestMatch = $data['fingerprint'];
            }
        }
        
        return $bestMatch;
    }

    /**
     * 计算指纹相似度
     */
    private function calculateFingerprintSimilarity($fingerprint1, $fingerprint2)
    {
        if (strlen($fingerprint1) !== strlen($fingerprint2)) {
            return 0;
        }
        
        $distance = 0;
        $length = strlen($fingerprint1);
        
        for ($i = 0; $i < $length; $i++) {
            if ($fingerprint1[$i] !== $fingerprint2[$i]) {
                $distance++;
            }
        }
        
        return 1 - ($distance / $length);
    }

    /**
     * 验证指纹有效性
     */
    private function isFingerprintValid($cachedInfo)
    {
        // 检查指纹是否过期
        if (isset($cachedInfo['timestamp'])) {
            $age = time() - $cachedInfo['timestamp'];
            if ($age > self::FINGERPRINT_CACHE_TTL) {
                return false;
            }
        }
        
        // 检查设备信息是否发生显著变化
        if (isset($cachedInfo['device_info'])) {
            $stabilityScore = $this->calculateStabilityScore($cachedInfo['device_info']);
            if ($stabilityScore < 0.5) {
                return false;
            }
        }
        
        return true;
    }

    /**
     * 缓存指纹
     */
    private function cacheFingerprint($fingerprint, $deviceInfo)
    {
        $cacheData = [
            'fingerprint' => $fingerprint,
            'device_info' => $deviceInfo,
            'timestamp' => time(),
            'hit_count' => 1
        ];
        
        // 检查是否已存在
        $existing = $this->getCachedFingerprint($fingerprint);
        if ($existing) {
            $cacheData['hit_count'] = $existing['hit_count'] + 1;
        }
        
        $cacheKey = "device_fingerprint:{$fingerprint}";
        Redis::setex($cacheKey, self::FINGERPRINT_CACHE_TTL, json_encode($cacheData));
    }

    /**
     * 获取缓存的指纹
     */
    private function getCachedFingerprint($fingerprint)
    {
        $cacheKey = "device_fingerprint:{$fingerprint}";
        $cachedData = Redis::get($cacheKey);
        
        return $cachedData ? json_decode($cachedData, true) : null;
    }

    /**
     * 获取验证详情
     */
    private function getValidationDetails($currentInfo, $cachedInfo)
    {
        $details = [];
        
        // 比较各个特征
        $features = ['platform', 'device_model', 'screen_resolution', 'language', 'timezone'];
        
        foreach ($features as $feature) {
            $current = $currentInfo[$feature] ?? null;
            $cached = $cachedInfo[$feature] ?? null;
            
            if ($current !== $cached) {
                $details[] = [
                    'feature' => $feature,
                    'current' => $current,
                    'cached' => $cached,
                    'match' => false
                ];
            } else {
                $details[] = [
                    'feature' => $feature,
                    'match' => true
                ];
            }
        }
        
        return $details;
    }

    // 标准化方法
    private function normalizePlatform($platform)
    {
        $platform = strtolower(trim($platform));
        $platforms = [
            'ios' => 'ios',
            'android' => 'android',
            'windows' => 'windows',
            'macos' => 'macos',
            'linux' => 'linux',
            'web' => 'web'
        ];
        
        return $platforms[$platform] ?? 'unknown';
    }

    private function normalizeVersion($version)
    {
        return preg_replace('/[^0-9.]/', '', $version);
    }

    private function normalizeDeviceModel($model)
    {
        return preg_replace('/[^a-zA-Z0-9-_\s]/', '', trim($model));
    }

    private function normalizeResolution($resolution)
    {
        if (preg_match('/(\d+)x(\d+)/', $resolution, $matches)) {
            return $matches[1] . 'x' . $matches[2];
        }
        return 'unknown';
    }

    private function normalizeUserAgent($userAgent)
    {
        return substr(trim($userAgent), 0, 500); // 限制长度
    }

    private function normalizeLanguage($language)
    {
        return strtolower(substr(trim($language), 0, 10));
    }

    private function normalizeTimezone($timezone)
    {
        return trim($timezone);
    }

    private function normalizeBatteryLevel($level)
    {
        if ($level === null) return null;
        return max(0, min(100, intval($level)));
    }

    private function normalizeNetworkType($network)
    {
        $network = strtolower(trim($network));
        $types = ['wifi', '4g', '5g', '3g', '2g', 'ethernet', 'unknown'];
        return in_array($network, $types) ? $network : 'unknown';
    }

    private function normalizeMacAddress($mac)
    {
        $mac = strtolower(trim($mac));
        if (preg_match('/^([0-9a-f]{2}[:-]){5}[0-9a-f]{2}$/', $mac)) {
            return $mac;
        }
        return '';
    }

    private function normalizeFonts($fonts)
    {
        if (!is_array($fonts)) return [];
        return array_slice(array_unique(array_map('trim', $fonts)), 0, 50); // 限制数量
    }

    private function normalizePlugins($plugins)
    {
        if (!is_array($plugins)) return [];
        return array_slice(array_unique(array_map('trim', $plugins)), 0, 20); // 限制数量
    }

    private function getTimeSegment($hour)
    {
        if ($hour >= 0 && $hour < 6) return 'night';
        if ($hour >= 6 && $hour < 12) return 'morning';
        if ($hour >= 12 && $hour < 18) return 'afternoon';
        return 'evening';
    }

    private function getBatterySegment($level)
    {
        if ($level <= 20) return 'low';
        if ($level <= 50) return 'medium';
        if ($level <= 80) return 'high';
        return 'full';
    }

    private function getIpClass()
    {
        $ip = $this->getClientIp();
        $parts = explode('.', $ip);
        
        if (count($parts) >= 1) {
            $firstOctet = intval($parts[0]);
            if ($firstOctet >= 1 && $firstOctet <= 126) return 'A';
            if ($firstOctet >= 128 && $firstOctet <= 191) return 'B';
            if ($firstOctet >= 192 && $firstOctet <= 223) return 'C';
            if ($firstOctet >= 224 && $firstOctet <= 239) return 'D';
            if ($firstOctet >= 240 && $firstOctet <= 255) return 'E';
        }
        
        return 'unknown';
    }

    private function parseUserAgent($userAgent)
    {
        $features = [];
        
        // 浏览器类型
        if (stripos($userAgent, 'chrome') !== false) $features['browser'] = 'chrome';
        elseif (stripos($userAgent, 'firefox') !== false) $features['browser'] = 'firefox';
        elseif (stripos($userAgent, 'safari') !== false) $features['browser'] = 'safari';
        elseif (stripos($userAgent, 'edge') !== false) $features['browser'] = 'edge';
        else $features['browser'] = 'unknown';
        
        return $features;
    }

    private function getClientIp()
    {
        $ipKeys = ['HTTP_X_FORWARDED_FOR', 'HTTP_X_REAL_IP', 'REMOTE_ADDR'];
        foreach ($ipKeys as $key) {
            if (!empty($_SERVER[$key])) {
                $ips = explode(',', $_SERVER[$key]);
                return trim($ips[0]);
            }
        }
        return '0.0.0.0';
    }
}